package com.yang.producer;

import com.yang.message.Demo10Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @ClassName Demo10Producer
 * @Description: <顺序消息>的定义：
 *         普通顺序消息：Producer 将关联的消息发送到相同的消息队列。
 *         完全严格顺序：在【普通顺序消息】的基础上，Consumer 严格顺序消费
 *     在RabbitMQ上，实现顺序消息需要做两个事情。
 *     1. 事情一：我们需要保证RabbitMQ Producer 发送相关联的消息发送到相同Queue中。
 *        例如说，我们要发送用户信息发生变更的Message，那么如果我们希望使用顺序消息的情况下，可以将用户编号相同的消息发送到相同的Queue中。
 *     2. 事情二，我们在有且仅启动一个Consumer消费该队列，保证Consumer严格顺序消费。
 *     不过如果这样做，会存在两个问题，我们逐个来看看。
 *     1. 问题一，如果我们将消息仅仅投递到一个Queue中，并且采用单个Consumer串行消费，在监听的Queue每秒消息量比较大的时候，会导致消费不及时，导致消息积压的问题
 *     此时，我们有两种方案来解决：
 *          方案一，在Producer端，将Queue拆成多个_子Queuue_。假设原先Queue时Queue_User,那么我们就分拆成QUQUE_USER_00至QUEUE_USER_${N-1}这样N个队列，
 *                然后基于消息的用户编号取余，路由到对应的_子Queue_中。
 *          方案二，在Consumer端，将Queue拉取到的消息，将相关联的消息发送到_相同的线程_中来消费。
 *                 例如说，还是Queue是QUEUE_USER的例子，我们创建N个线程池大小为1的ExecutorService数组，然后基于消息的用户编号取余，提交到对应的ExecutorService中的单个线程来执行。
 *      两个方案并不冲突，可以结合使用。
 *     2. 问题二，如果我们启动相同Consumer的_多个进程_，会导致相同Queue的消息被分配到多个Consumer进行消费，破坏Consumer严格顺序消费。
 *
 *     此时，我们有两种方案来解决：
 *     方案一，引入Zookeeper来协调，动态设置多个进程中的相同Consumer的开关，保证有且仅有一个Consumer开启对同一个Queue的消费。
 *     方案二，仅适用于【问题一】的【方案一】。还是引入Zookeeper来协调，动态设置多个进程中的相同的Consumer消费的Queue的分配，保证有且仅有一个Consumer开启对同一个Queue的消费。
 * @Author yjl
 * @Date 2021/9/18
 * @Version V1.0
 */
@Component
public class Demo10Producer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息时，我们对 Demo10Message.id % 队列编号 进行取余，获得队列编号作为RoutingKey，从而路由消息到对应的子Queue中
     * @param id
     */
    public void syncSend(Integer id) {
        // 创建 Demo10Message 消息
        Demo10Message message = new Demo10Message();
        message.setId(id);
        // 同步发送消息
        rabbitTemplate.convertAndSend(Demo10Message.EXCHANGE, this.getRoutingKey(id), message);
    }

    private String getRoutingKey(Integer id) {
        return String.valueOf(id % Demo10Message.QUEUE_COUNT);
    }
}
